<html>
    <head>
        <title>对象映射</title>
    </head>
    <body>
        <script>
            // 对象映射

                    /*
                            将一个对象映射到另一个对象是很常见的。

                            这也很乏味和重复，因为通常两个类有具有相互映射的相同或相似的属性。

                            想象一下一个典型的应用程序服务方法：

                                    public class UserAppService : ApplicationService
                                    {
                                        private readonly IRepository<User, Guid> _userRepository;

                                        public UserAppService(IRepository<User, Guid> userRepository)
                                        {
                                            _userRepository = userRepository;
                                        }

                                        public async Task CreateUser(CreateUserInput input)
                                        {
                                            // 手动映射属性...
                                            var user = new User
                                            {
                                                Name = input.Name,
                                                Surname = input.Surname,
                                                EmailAddress = input.EmailAddress,
                                                Password = input.Password
                                            };

                                            await _userRepository.InsertAsync(user);
                                        }
                                    }

                            CreateUserInput是一个简单的DTO类，User是一个简单的实体。
                            
                            上面的代码从给定的输入对象创建一个User实体。User实体将在现实世界的应用程序中拥有更多属性，手动创建它将变得乏味且容易出错。
                            
                            当你向User和CreateUserInput类添加新属性时，你还必须更改映射代码。

                            我们可以使用库来自动处理这种映射，并有一个集成包来使用 AutoMapper作为对象映射器。

                    */


                    /*
                        一、IObjectMapper

                                IObjectMapper接口（在 "Volo. Abp.ObjectMap" 包中）定义了一个简单的Map方法。
                                
                                之前介绍的示例代码可以重写如下：

                                    public class UserAppService : ApplicationService
                                    {
                                        private readonly IRepository<User, Guid> _userRepository;

                                        public UserAppService(IRepository<User, Guid> userRepository)
                                        {
                                            _userRepository = userRepository;  // 
                                        }

                                        public async Task CreateUser(CreateUserInput input)
                                        {
                                            // 自定映射
                                            var user = ObjectMapper.Map<CreateUserInput, User>(input);

                                            await _userRepository.InsertAsync(user);
                                        }
                                    }

                                ObjectMapper在本例的ApplicationService基类中定义。你可以在其他地方需要时直接注入IObjectMapper接口。


                                Map方法有两个通用参数： 第一个是源对象类型，而第二个是目标对象类型。


                                如果需要设置现有对象的属性，可以使用Map方法的第二个重载：

                                    public class UserAppService : ApplicationService
                                    {
                                        private readonly IRepository<User, Guid> _userRepository;

                                        public UserAppService(IRepository<User, Guid> userRepository)
                                        {
                                            _userRepository = userRepository;
                                        }

                                        public async Task UpdateUserAsync(Guid id, UpdateUserInput input)
                                        {
                                            var user = await _userRepository.GetAsync(id); // 获取用户实体独享

                                            // 自动创建映射
                                            ObjectMapper.Map<UpdateUserInput, User>(input, user); // 指定往实体对象中映射

                                            await _userRepository.UpdateAsync(user);
                                        }
                                    }


                                你应该在使用映射对象之前定义映射文档。请参阅AutoMapper集成部分以了解如何定义映射。
                    */

                    /*
                        二、AutoMapper集成

                                AutoMapper是最流行的对象到对象映射库之一。
                                
                                "Volo. Abp.AutoMapper"  包定义了IObjectMapper的AutoMapper集成。
                                
                                定义如下所述的映射后，你可以像前面解释的那样使用IObjectMapper接口。

                                1、定义映射 (映射文档)

                                       定义对象映射的一种方法是创建一个 Profile类。 例子：

                                            public class MyProfile : Profile
                                            {
                                                public MyProfile()
                                                {
                                                    CreateMap<User, UserDto>();
                                                }
                                            }

                                2、使用 AbpAutoMapperOptions注册配置文件：

                                        [DependsOn(typeof(AbpAutoMapperModule))]
                                        public class MyModule : AbpModule
                                        {
                                            public override void ConfigureServices(ServiceConfigurationContext context)
                                            {
                                                Configure<AbpAutoMapperOptions>(options =>
                                                {
                                                    //  AddMaps 方法注册在给定类所在程序集中所有的映射文档类。
                                                    options.AddMaps<MyModule>();
                                                });
                                            }
                                        }

                                3、配置验证
                                        
                                        AddMaps可选地采用bool参数来控制模块的配置验证：

                                                options.AddMaps<MyModule>(validate: true);

                                        虽然此选项默认为 false ,但建议启用配置验证作为最佳实践。



                                        可以使用AddProfile而不是AddMaps来控制每个映射文档类的配置验证：

                                                options.AddProfile<MyProfile>(validate: true);

                                        ★、如果你有多个映射文档类，并且只需要为其中的几个启用验证，请先使用 AddMaps 而不进行验证，然后对要验证的的每个映射文档类使用AddProfile.


                                4、对象扩展的映射

                                        对象扩展系统允许为现有类定义额外的属性。ABP提供了一个映射定义扩展来正确映射两个对象爱过你的额外属性。

                                                public class MyProfile : Profile
                                                {
                                                    public MyProfile()
                                                    {
                                                        CreateMap<User, UserDto>()
                                                            .MapExtraProperties();
                                                    }
                                                }

                                        如果两个类都是可扩展对象，建议使用MapExtraProperties()方法（实现IHasExtraProperties接口）。有关更多信息，请参阅对象扩展文档。


                                5、其他有用的扩展方法

                                        还有一些扩展方法可以假话你的映射代码：

                                        ①、忽略审计属性：  

                                                   当你将一个对象映射到另一个对象时，通常会忽略审核属性。

                                                   假设你需要将ProductDto（DTO）映射到Productentity并且该实体继承自AuditedEntity类（它提供了CreationTime、CreatorId、IHasModificationTime…等属性）。

                                                   在从DTO映射时，你可能希望忽略这些基本属性。
                                                   
                                                   你可以使用IgnoreAuditedObjectProperties()方法忽略所有审计属性（而不是一个接一个地手动忽略它们）：

                                                            public class MyProfile : Profile
                                                            {
                                                                public MyProfile()
                                                                {
                                                                    CreateMap<ProductDto, Product>()
                                                                        .IgnoreAuditedObjectProperties(); // 忽略所有审计属性

                                                                }
                                                            }


                                                    还有更多的扩展方法，如IgnoreFullAuditedObjectProperties()和IgnoreCreationAuditedObjectProperties()，它们可以根据你的实体类型使用。

                                                    请参阅实体文档中的“审核属性的基类和接口”部分以了解有关审核属性的更多信息。


                                        ②、忽略其他属性

                                                    在AutoMapper中，你通常编写这样的映射代码来忽略属性：

                                                            public class MyProfile : Profile
                                                            {
                                                                public MyProfile()
                                                                {
                                                                    CreateMap<SimpleClass1, SimpleClass2>()
                                                                        .ForMember(x => x.CreationTime, map => map.Ignore());
                                                                }
                                                            }

                                                    这样代码太长了，在abp中，你可以直接使用 Ignore()扩展方法：

                                                            public class MyProfile : Profile
                                                            {
                                                                public MyProfile()
                                                                {
                                                                    CreateMap<SimpleClass1, SimpleClass2>()
                                                                        .Ignore(x => x.CreationTime);
                                                                }
                                                            }
                    */

                    /*
                        三、高级主题

                                1、IObjectMapper接口

                                            假设你已经创建了一个可重用模块，它定义了AutoMapper配置文件并在需要映射对象时使用IObjectMapper。
                                            
                                            然后，根据模块化的性质，你的模块可以在不同的应用程序中使用。

                                            IObjectMapper是一个抽象，可以被最终应用程序替换以使用另一个映射库。这里的问题是，你的可重用模块被设计为使用AutoMapper库，因为它只为其定义映射。
                                            
                                            在这种情况下，你将希望保证你的模块始终使用AutoMapper，即使最终应用程序使用另一个默认对象映射库。

                                            IObjectMapper<TContext> 用于"上下文化"对象映射，因此你可以为不同的模块/上下文使用不同的映射库：

                                                    eg: 使用方法

                                                        public class UserAppService : ApplicationService
                                                        {
                                                            private readonly IRepository<User, Guid> _userRepository;   
                                                            
                                                            private readonly IObjectMapper<MyModule> _objectMapper;

                                                            public UserAppService(
                                                                IRepository<User, Guid> userRepository, 
                                                                IObjectMapper<MyModule> objectMapper) //Inject module specific mapper
                                                            {
                                                                _userRepository = userRepository;
                                                                _objectMapper = objectMapper;
                                                            }

                                                            public async Task CreateUserAsync(CreateUserInput input)
                                                            {
                                                                //Use the module specific mapper
                                                                var user = _objectMapper.Map<CreateUserInput, User>(input);

                                                                await _userRepository.InsertAsync(user);
                                                            }
                                                        }

                                                    UserAppService注入IObjectMapper<MyModule>，即该模块的特定对象映射器。它的用法与IObjectMapper完全相同。

                                                    上面的示例代码不使用ApplicationService中定义的ObjectMapper属性，而是注入IObjectMapper<MyModule>。

                                                     但是，仍然可以使用基本属性，因为ApplicationService定义了一个可以在类构造函数中设置的ObjectMapperContext属性   

                                                    所以，关于的例子可以重写如下：

                                                            public class UserAppService : ApplicationService
                                                            {
                                                                private readonly IRepository<User, Guid> _userRepository;

                                                                public UserAppService(IRepository<User, Guid> userRepository)
                                                                {
                                                                    _userRepository = userRepository;

                                                                    // 设置映射上下文
                                                                    ObjectMapperContext = typeof(MyModule); // 设置当前服务的映射上下文(使用哪个模块的映射)
                                                                }

                                                                public async Task CreateUserAsync(CreateUserInput input)
                                                                {
                                                                    // 设置当前服务的映射上下文后使用普通映射器
                                                                    var user = ObjectMapper.Map<CreateUserInput, User>(input);

                                                                    await _userRepository.InsertAsync(user);
                                                                }
                                                            }

                                            虽然使用上"下文化对象映射器"与普通对象映射器相同，但你应该在模块的ConfigureServices 方法中注册"上下文化映射器"：、

                                                    [DependsOn(typeof(AbpAutoMapperModule))]
                                                    public class MyModule : AbpModule
                                                    {
                                                        public override void ConfigureServices(ServiceConfigurationContext context)
                                                        {
                                                            // 为MyModule模块使用AutoMapper
                                                            context.Services.AddAutoMapperObjectMapper<MyModule>();

                                                            Configure<AbpAutoMapperOptions>(options =>
                                                            {
                                                                options.AddMaps<MyModule>(validate: true);
                                                            });
                                                        }
                                                    }


                                        IObjectMapper<MyModule>是可重用模块的基本功能，它可以在多个应用程序中使用，每个应用程序都可以使用不同的库进行对象到对象的映射。

                                         所有预构建的ABP模块都在使用它。但是，对于最终的应用程序，你可以忽略此接口并始终使用默认的IObjectMapper接口。   



                            2、IObjectMapper<TSource， TDestination>接口

                                        ABP允许你自定义特定类的映射代码。
                                        
                                        假设你要创建一个自定义类以从User映射到UserDto。在这种情况下，你可以创建一个实现IObjectMapper<User, UserDto>的类：

                                            public class MyCustomUserMapper : IObjectMapper<User, UserDto>, ITransientDependency
                                            {
                                                public UserDto Map(User source)
                                                {
                                                    //TODO: Create a new UserDto
                                                }

                                                public UserDto Map(User source, UserDto destination)
                                                {
                                                    //TODO: 映射到现有Dto对象
                                                    return destination;
                                                }
                                            }

                                        ABP会自动发现并注册MyCustomUserMapper，并且每当你使用IObjectMapper将User映射到UserDto时，它会自动使用。

                                        单个类可以为不同的对象对实现多个IObjectMapper<TSource, TDestination>。

                                        种方法很强大，因为MyCustomUserMapper可以注入任何其他服务并在Map方法中使用。


                                        一旦实现了IObjectMapper<User, UserDto>，ABP就可以自动将User对象的集合转换为UserDto对象的集合:

                                                      IEnumerable<T>

                                                      ICollection<T>

                                                      Collection<T>

                                                      IList<T>

                                                      List<T>

                                                      T[] (array) T[]（数组） 

                                            eg: 
                                                    var users = await _userRepository.GetListAsync(); // returns List<User>
                                                    var dtos = ObjectMapper.Map<List<User>, List<UserDto>>(users); // creates List<UserDto>
                    */
        </script>
    </body>
</html>